﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using XNADota.MDX;
using XNADota.Terrain;
using System.Diagnostics;

namespace XNADota.Graphics
{
    #region 2D Graphics
    public class SpriteInstance : PaletteInstance
    {
        private int _X;
        private int _Y;
        private int _Width;
        private int _Height;

        private float _SX;
        private float _SY;
        private float _SWidth;
        private float _SHeight;

        private Texture2D _Texture;

        public SpriteInstance(int X, int Y, int Width, int Height,
                              float SX, float SY, float SW, float SH,
                              Texture2D texture, PaletteRenderer palette)
            : base(Vector3.Zero, Vector3.Zero, 0, palette)
        {
            Debug.Assert(texture != null);

            _X = X;
            _Y = Y;
            _Width = Width;
            _Height = Height;

            _SX = SX;
            _SY = SY;
            _SWidth = SW;
            _SHeight = SH;

            _Texture = texture;
        }

        public void Draw()
        {
            // render in default size
            Draw(SceneManager.DefaultScreenWidth, SceneManager.DefaultScreenHeight);
        }

        public void Draw(int ScreenWidth, int ScreenHeight)
        {
            int Width = _Texture.Width;
            int Height = _Texture.Height;

            int SX = (int)(_SX * Width);
            int SY = (int)(_SY * Height);
            int SWidth = (int)(_SWidth * Width);
            int SHeight = (int)(_SHeight * Height);

            int TX = _X * ScreenWidth / SceneManager.DefaultScreenWidth;
            int TY = _Y * ScreenHeight / SceneManager.DefaultScreenHeight;
            int TW = _Width * ScreenWidth / SceneManager.DefaultScreenWidth;
            int TH = _Height * ScreenHeight / SceneManager.DefaultScreenHeight;

            SpriteBatch spriteBatch = (this.PaletteReference.PaletteRenderer as SpriteRenderer).SpriteBatch;
            spriteBatch.Draw(_Texture, new Rectangle(TX, TY, TW, TH),
                new Rectangle(SX, SY, SWidth, SHeight), Color.White);
        }
    }
    #endregion

    #region 3D Graphics
    public class PaletteInstance
    {
        public PaletteInstance(Vector3 Position, Vector3 Rotation, ERenderState RenderStates, PaletteRenderer palette)
        {
            this.Position = Position;
            this.Orientation = Matrix.CreateFromYawPitchRoll(Rotation.X, Rotation.Y, Rotation.Z);
            this.RenderStates = RenderStates;
            this.PaletteReference = new PaletteReference(palette);
            this.IsMovable = false;
            this.IsSelectable = false;
        }

        public Vector3 Scale;
        public Vector3 Position;
        public Matrix Orientation;
        public ERenderState RenderStates;
        public bool IsVisible;
        public bool IsMovable;
        public bool IsSelectable;
        public bool IsSelected;
        public Vector3 SelectableCircleScale = Vector3.One * 1.0f;

        public PaletteReference PaletteReference;

        public virtual void Update(GameTime gameTime)
        {
            // empty
        }

        public virtual void Update(BoundingFrustum BoundingFrustum)
        {
            if (BoundingFrustum.Contains(this.Position) != ContainmentType.Disjoint)
                IsVisible = true;
            else
                IsVisible = false;
        }

        public virtual void SetSelected(bool selected)
        {
            // do nothing
        }

        public virtual bool Select(Ray ray)
        {
            return false;
        }

        public virtual ContainmentType BeContained(BoundingBox BoundingBox)
        {
            return BoundingBox.Contains(this.Position);
        }

        public virtual void Draw(Matrix view, Matrix projection)
        {
            if (IsVisible == false)
                return;

            ModelRenderer renderer = this.PaletteReference.PaletteRenderer as ModelRenderer;
            Matrix world = Orientation * Matrix.CreateTranslation(this.Position);
            renderer.Draw(world, view, projection);

            IsVisible = false;
        }
    }


    public class LandscapeCellInstance : PaletteInstance
    {
        private BoundingBox _BoundingBox;

        public LandscapeCellInstance(LandscapePatchRenderer Renderer)
            : base(Renderer.Position, Vector3.Zero, ERenderState.Opaque | ERenderState.EnableDepth | ERenderState.CullCounterClockWise | ERenderState.Solid, Renderer)
        {
            _BoundingBox = Renderer.BoundingBox;
        }

        public override void Update(BoundingFrustum BoundingFrustum)
        {
            if (BoundingFrustum.Contains(this._BoundingBox) != ContainmentType.Disjoint)
                IsVisible = true;
            else
                IsVisible = false;
        }

        public override ContainmentType BeContained(BoundingBox BoundingBox)
        {
            return BoundingBox.Contains(_BoundingBox);
        }

        public override void Draw(Matrix view, Matrix projection)
        {
            if (IsVisible == false)
                return;

            LandscapePatchRenderer renderer = this.PaletteReference.PaletteRenderer as LandscapePatchRenderer;
            renderer.Draw(Matrix.Identity, view, projection);

            IsVisible = false;
        }
    }

    public class CliffInstance : PaletteInstance
    {
        private Texture2D _Texture;
        private Vector3[] _Positions;
        private bool _IsCliffTrans;
        private EDirection _Direction;
        private ESide _Side;
        private BoundingBox _BoundingBox;

        private CliffRenderer _CliffRenderer;

        public CliffInstance(Vector3 Position0, Vector3 Position1, Vector3 Position2, Vector3 Position3, Vector3 Position4, Vector3 Position5,
                             bool isCliffTrans, EDirection direction, ESide side,
                             Texture2D texture, Vector3 Position, Vector3 Rotation, ERenderState RenderStates, CliffRenderer palette) :
            base(Position, Rotation, RenderStates, palette)
        {
            this._Texture = texture;
            _Positions = new Vector3[6];
            _Positions[0] = Position0;
            _Positions[1] = Position1;
            _Positions[2] = Position2;
            _Positions[3] = Position3;
            _Positions[4] = Position4;
            _Positions[5] = Position5;
            _IsCliffTrans = isCliffTrans;
            _Direction = direction;
            _Side = side;

            Vector3 Min = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);
            Vector3 Max = new Vector3(float.MinValue, float.MinValue, float.MinValue);
            var MaxIndex = (_IsCliffTrans ? 6 : 4);
            for (int i = 0; i < MaxIndex; i++)
            {
                Min.X = Math.Min(_Positions[i].X, Min.X);
                Max.X = Math.Max(_Positions[i].X, Max.X);

                Min.Y = Math.Min(_Positions[i].Y, Min.Y);
                Max.Y = Math.Max(_Positions[i].Y, Max.Y);

                Min.Z = Math.Min(_Positions[i].Z, Min.Z);
                Max.Z = Math.Max(_Positions[i].Z, Max.Z);
            }

            _BoundingBox = new BoundingBox(Min, Max);

            _CliffRenderer = (this.PaletteReference.PaletteRenderer as CliffRenderer).Copy();
            _CliffRenderer.UpdateRelpacableTexture(_Texture);
            Matrix world = Matrix.CreateFromYawPitchRoll(Rotation.X, Rotation.Y, Rotation.Z) * Matrix.CreateTranslation(Position);
            _CliffRenderer.UpdateCliff(_Positions, _IsCliffTrans, _Direction, _Side, world);
        }

        public override ContainmentType BeContained(BoundingBox BoundingBox)
        {
            return BoundingBox.Contains(_BoundingBox);
        }

        public override void Update(BoundingFrustum BoundingFrustum)
        {
            if (BoundingFrustum.Contains(this._BoundingBox) != ContainmentType.Disjoint)
                IsVisible = true;
            else
                IsVisible = false;
        }

        public override void Draw(Matrix view, Matrix projection)
        {
            if (IsVisible == false)
                return;

            _CliffRenderer.Draw(Matrix.Identity, view, projection);

            IsVisible = false;
        }
    }

    public class DoodadInstance : PaletteInstance
    {
        protected Texture2D _ReplacableTexture;

        protected SequencePlayer _ActionPlayer;

        public Texture2D ReplacableTexture
        {
            set { _ReplacableTexture = value; }
            get { return _ReplacableTexture; }
        }

        public DoodadInstance(Vector3 Position, Vector3 Scale, Vector3 Rotation, ERenderState RenderStates, DoodadRenderer palette)
            : base(Position, Rotation, RenderStates, palette)
        {
            this.Scale = Scale;

            if (palette.Player != null)
            {
                int toPlayIndex = palette.Player.FindName("Stand");
                if (toPlayIndex != -1)
                {
                    _ActionPlayer = new SequencePlayer(palette.Player[toPlayIndex]);
                    _ActionPlayer.Start(1.0f);
                }
                else
                {
                    //throw new Exception("Stand animation is not found.");
                }
            }
        }

        public override void Update(GameTime gameTime)
        {
            if (_ActionPlayer != null)
                _ActionPlayer.Update(gameTime.ElapsedGameTime);
        }

        public override void Draw(Matrix view, Matrix projection)
        {
            if (IsVisible == false)
                return;

            ModelRenderer renderer = this.PaletteReference.PaletteRenderer as ModelRenderer;
            if (_ReplacableTexture != null)
                renderer.UpdateRelpacableTexture(_ReplacableTexture);
            Matrix world = Orientation *
                           Matrix.CreateScale(this.Scale) *
                           Matrix.CreateTranslation(this.Position);

            if (_ActionPlayer != null)
            {
                renderer.UpdateAnimationTick(_ActionPlayer.CurrentTick);
            }

            renderer.Draw(world, view, projection);

            IsVisible = false;
        }
    }

    public class UnitInstance : DoodadInstance
    {
        public UnitInstance(Vector3 Position, Vector3 Scale, Vector3 Rotation, ERenderState RenderStates, UnitRenderer palette)
            : base(Position, Scale, Rotation, RenderStates, palette)
        {
            if (palette.Player != null)
            {
                int toPlayIndex = palette.Player.FindName("stand");
                if (toPlayIndex == -1)
                    toPlayIndex = palette.Player.FindName("stand 1");
                if (toPlayIndex == -1)
                    toPlayIndex = palette.Player.FindName("stand - 1");
                if (toPlayIndex != -1)
                {
                    _ActionPlayer = new SequencePlayer(palette.Player[toPlayIndex]);
                    _ActionPlayer.Start(1.0f);
                }
                else
                {
                    _ActionPlayer = new SequencePlayer(palette.Player[0]);
                    _ActionPlayer.Start(1.0f);
                    //throw new Exception("Stand animation is not found.");
                }
            }
        }

        public override void Update(GameTime gameTime)
        {
            if (_ActionPlayer != null)
                _ActionPlayer.Update(gameTime.ElapsedGameTime);
        }

        public override void Update(BoundingFrustum BoundingFrustum)
        {
            if (BoundingFrustum.Contains(this.Position) != ContainmentType.Disjoint)
                IsVisible = true;
            else
                IsVisible = false;
        }
    }
    #endregion
}
